package com.lizhenhua.fmmall.service.serviceImpl;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.lizhenhua.fmmall.entity.*;
import com.lizhenhua.fmmall.mapper.OrderItemMapper;
import com.lizhenhua.fmmall.mapper.OrdersMapper;
import com.lizhenhua.fmmall.mapper.ProductSkuMapper;
import com.lizhenhua.fmmall.mapper.ShoppingCartMapper;
import com.lizhenhua.fmmall.service.OrderService;
import com.lizhenhua.fmmall.utils.PageHelper;
import com.lizhenhua.fmmall.vo.ResStatus;
import com.lizhenhua.fmmall.vo.ResultVo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private ProductSkuMapper skuMapper;
    @Autowired
    private OrdersMapper ordersMapper;
    @Autowired
    private OrderItemMapper orderItemMapper;
    @Autowired
    private ObjectMapper objectMapper;
    private StringRedisTemplate stringRedisTemplate;
    // 日志文件
    private Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class);
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 保存订单业务
     */

// 事务只有全部执行成功 才会提交
    @Transactional
    public Map<String, String> addOrder(
            String cids, Orders orders
    ) throws SQLException {
        logger.info("add order begin....");

        Map<String, String> map = null;

        // 把字符串分割为数组
        String[] split = cids.split(",");
        // 把每个字符 添加到int类型的数组里面
        List<Integer> cidsList = new ArrayList<>();
        for (String s : split
        ) {
            cidsList.add(Integer.parseInt(s));
        }

        // 获取所有skuid
        //1.校验库存 根据cids查询当前订单中关联的购物车记录详情
        List<ShoppingCartVo> list1 = shoppingCartMapper.selectShopListByCartId(cidsList);

        // 创建一个数组 记录被加锁的id
        String[] skuIds = new String[list1.size()];

        boolean isLock = true;
        Map<String, RLock> locks = new HashMap<>();
        for (int j = 0; j < list1.size(); j++) {
            String skuId = list1.get(j).getSkuId();

            // 获取锁
            boolean b = false;
            try {
                // 用Redisson 加锁 构建当前商品的锁
                RLock lock = redissonClient.getLock(skuId);
                // 这里有看门狗机制  锁十秒  如果事务还没有结束 锁只剩下三秒的话  自动续命十秒  只要没结束就会一直续命
                b = lock.tryLock(10, 3, TimeUnit.SECONDS); // 并发情况  假如有两个线程给同一个商品减库存 只要我第一次锁住了 第二次就会返回false
                // 一旦加锁成功就要把 加锁的商品记录下来
                if (b) {
                    // 把 被加锁的skuId 记录到数组里面
                    skuIds[j] = skuId;
                    // 设置对应id的锁  方面以后释放  这里是加锁成功的id
                    locks.put(skuId, lock);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            isLock = isLock && b;
        }

        //底层代码
      /*
        String s1 = null;
        // 创建一个数组 记录被加锁的id
        String[] skuIds = new String[list1.size()];

        int i = 0;
        Map<String, String> values = new HashMap<>();
        for (int j = 0; j < list1.size(); j++) {
            String sid = list1.get(j).getSkuId();
            // 订单中包含多个商品  每个skuId 表示一个商品
            // 假如出现故障  十秒钟自动过期 自动释放内存 但又会产生新问题
            //但又会产生新问题 比如由于网络原因 在第一次执行中过了十秒还没有完成以上事务
            // 那么存在这种情况   由于并发 第二次执行的时候他仍然会加锁成功  第一次的释放可能会释放第二次的锁
            String value = UUID.randomUUID().toString();
            Boolean ifAbsent = stringRedisTemplate.boundValueOps(sid).setIfAbsent(value, 10, TimeUnit.SECONDS);
            if (ifAbsent) {
                skuIds[i] = sid;
                values.put(sid, value);
            }
            isLock = isLock && ifAbsent;
        }

       */
        // 表示加锁成功 如果为true的话

        try {
            if (isLock) {
                List<ShoppingCartVo> list = shoppingCartMapper.selectShopListByCartId(cidsList);
                boolean bl = true;
                String untitled = ""; // 所有商品名称
                // 2. 检验库存 购物车的数量小于库存数量才可以
                for (ShoppingCartVo s : list
                ) {
                    if (Integer.parseInt(s.getCartNum()) > s.getSkuStock()) {
                        bl = false;
                    }
                    // 获取所有商品名称,以, 分割成字符串
                    untitled = untitled + s.getProductName() + ",";

                }
                // 2.保存订单
                if (bl) {
                    // 表示库存充足 ---- 保存订单
                    // 需要的参数
                    // 1. 用户的 userId
                    // 2. 用户提交商品的 cids
                    // 3. 收货地址信息  两种发发 1.根据收货地址的addrId 收货地址详情 2.收货人姓名地址电话 从前面传过来  再次用的方案2
                    // 4. 商品的总价格   可以从前端传过来
                    // 5. 支付方式
                    // 6. 订单创建时间
                    // 7. 订单初始状态
                    // 我们可以 通过Orders对象传参  除了cids
                    map = new HashMap<>();
                    logger.info("product stock is ok....");
                    orders.setUntitled(untitled);
                    orders.setCreateTime(new Date());
                    // 订单状态 1:待付款 2:待发货 3:待收货 4:待评价 5:已完成 6:已关闭
                    orders.setStatus("1");  // 初始状态为未支付
                    // 生成订单编号
                    String orderId = UUID.randomUUID().toString().replace("-", "");
                    orders.setOrderId(orderId);

                    int insert = ordersMapper.insert(orders);

                    // 3.生成商品快照

                    // 生成商品快照 每个购物车生成一个商品快照
                    for (ShoppingCartVo s : list
                    ) {
                        //new Random().nextInt(89999)+10000) 降低重复概率
                        Integer cnum = Integer.parseInt(s.getCartNum()); // 商品数量
                        Integer sPrice = Integer.parseInt(s.getSellPrice()); // 商品的售价
                        String itemId = System.currentTimeMillis() + "+" + (new Random().nextInt(89999) + 10000);
                        OrderItem orderItem = new OrderItem(
                                itemId, orderId, s.getProductId(), s.getProductName(),
                                s.getProductImg(), s.getSkuId(), s.getSkuName(), new BigDecimal(sPrice),
                                cnum, new BigDecimal(sPrice * cnum), new Date(), new Date(), 0);
                        int insert1 = orderItemMapper.insert(orderItem); // 一个一个保存  当我构造一个商品快照我就保存一个


                    }


                    // 4.扣减库存
                    // 根据套餐id skuId  修改库存量
                    // 商品1  奥利奥小饼干  套餐id 4  2  300
                    // 商品2  咪咪虾条     套餐id 1  2  200
                    // 你扣减 你选中的库存
                    // 再次遍历购物车
                    for (ShoppingCartVo s : list
                    ) {
                        String skuId = s.getSkuId();
                        int newStock = s.getSkuStock() - Integer.parseInt(s.getCartNum());
/*                Example example = new Example(ProductSku.class);
//                Example.Criteria criteria = example.createCriteria();
//                criteria.andEqualTo("skuId", skuId);
                 第一种方法
                这种方式操作了两次数据库 能做到 但是不建议这样做
                // 根据skuId  获取一条数据 productSku  是有属性值的
                ProductSku productSku = skuMapper.selectByPrimaryKey(example);
                //ProductSku productSku = new ProductSku();  这个没有属性值
                productSku.setStock(newStock);
                // updateByExample 根据条件 修改所有属性
                int key = skuMapper.updateByExample(productSku, example);

            第二种方法
                    // updateByExample 根据条件 修改所有属性
                    //updateByExampleSelective 只改指定的字段 所有字段为null的我不修改
                    // 再此 我只修改了字段名为 stock 的值
               int key = skuMapper.updateByExampleSelective(productSku, example);
*/
//              第三种方法
                        // 根据主键修改 属性值
                        ProductSku productSku = new ProductSku();
                        productSku.setSkuId(skuId);
                        productSku.setStock(newStock);
                        int k = skuMapper.updateByPrimaryKeySelective(productSku);
                    }

                    // 释放锁 底层代码
                 /*
                for (String s : skuIds
                ) {
                    // 清除 key 相当于释放锁
                    if (s != null && !"".equals("")) {
                        String skuId = stringRedisTemplate.boundValueOps(s).get();
                        if (skuId.equals(values.get(skuId)) && skuId != null) {
                            stringRedisTemplate.delete(s);
                        }

                    }
                }
*/

                    // 5. 当购物车里面的东西购买成功之后 会把下单的商品删除掉
                    for (Integer i1 : cidsList
                    ) {
                        shoppingCartMapper.deleteByPrimaryKey(i1);
                    }
                    logger.info("add order finished....");
                    map.put("orderId", orderId);
                    map.put("productNames", untitled);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 订单添加失败
            // 释放锁 底层代码
            // 当加锁失败时  可能部分商品已经被锁住 所以我们要把他们释放
       /*    for (String s : skuIds
            ) {
                // 清除 key 相当于释放锁
                if (s != null && !"".equals("")) {
                    stringRedisTemplate.delete(s);
                }
            }*/
            // Redisson 框架释放锁
            for (String skuId : skuIds
            ) {
                if (skuId != null && !"".equals(skuId)) {
                    // 根据skuId 释放锁
                    locks.get(skuId).unlock();
                }
            }
        }

        return map;
    }


    @Override
    public int updateOrderStatus(String orderId, String status) {
        Orders orders = new Orders();
        orders.setStatus(status);
        orders.setOrderId(orderId);
        int i = ordersMapper.updateByPrimaryKeySelective(orders);
        return i;
    }

    @Override
    public ResultVo getOrderById(String orderId) {
        Orders orders = ordersMapper.selectByPrimaryKey(orderId);
        return new ResultVo(ResStatus.OK, "success", orders.getStatus());
    }

    //isolation： 隔离  SERIALIZABLE: 串行化  比如有两个人同时访问 等我做完了 下一个人在做
    @Transactional(isolation = Isolation.SERIALIZABLE) // 事务锁
    public void closeOrder(String orderId) {
        //synchronized (this) 上锁 让他可以串行化
        synchronized (this) { // jvm锁

            Orders orders1 = new Orders();
            orders1.setOrderId(orderId);
            orders1.setStatus("6");// 已关闭的订单
            orders1.setCloseType(1); // 关闭类型超时未支付
            // 修改指定字段的内容
            int i = ordersMapper.updateByPrimaryKeySelective(orders1);

            //c 还原库存  根据当前订单编号查询快照的信息 （skuId by_count） 修改product_sku表 还原库存

            Example example2 = new Example(OrderItem.class);
            Example.Criteria criteria2 = example2.createCriteria();
            criteria2.andEqualTo("orderId", orderId);

            List<OrderItem> orderItems = orderItemMapper.selectByExample(example2);

            for (OrderItem orderItem : orderItems
            ) {
                Example example1 = new Example(ProductSku.class);
                Example.Criteria criteria1 = example1.createCriteria();
                criteria1.andEqualTo("skuId", orderItem.getSkuId());

                // 获取id 获取当前套餐信息
                ProductSku productSku = skuMapper.selectByPrimaryKey(orderItem.getSkuId());
                // 设置库存
                productSku.setStock(productSku.getStock() + orderItem.getBuyCounts());
                // 修改库存
                skuMapper.updateByExampleSelective(productSku, example1);

            }


        }

    }

    @Override
    public ResultVo listOrders(String userId, String status, int pageNum, int limit) {
        int start = (pageNum - 1) * limit;
        List<OrdersVo> ordersVos = ordersMapper.selectOrdersByUserId(userId, status, start, limit);
        Example example = new Example(Orders.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("userId", userId);
        if (status != null && !"".equals(status)) {
            criteria.andEqualTo("status", status);
        }

        int count = ordersMapper.selectCountByExample(example);
        //总页码
        int pageCount = count % limit == 0 ? count / limit : count / limit + 1;
        return new ResultVo(ResStatus.OK, "success", new PageHelper<OrdersVo>(count, pageCount, ordersVos));


    }


}
